2019-04-28 | 重学前端系列笔记 | UNLOCK

16. javascript执行顺序

前言

当我们接到一段 JavaScript 代码的时候,传递给 JavaScript 引擎要求其执行。在遇到事件的时候,会继续将其中的一些事件代码交给 JavaScript 引擎执行,当然也会提供一些 APIJavaScript 引擎,这样的 API 允许引擎在特定时机执行某段代码。
我们可以理解为 JavaScript 引擎存在于内存中,它一直在运转,在等待着我们将 JavaScript 片段代码或者事件交给它进行执行处理,但是页面一般同时会有很多代码需要执行,那么就需要指定一个顺序。

宏任务与微任务

我们把宿主发起的任务称为宏观任务,把 JavaScript 引擎发起的任务称为微观任务。

JavaScript 引擎一直在等待着宿主分配宏观任务,这个等待分配,然后执行代码的过程,称之为 evenLoop

1
2
3
4
5
// 伪代码表述简单的evenLoop
while(true) {
r = wait(); // 等待宿主分配任务
execute(r); // 分配任务之后,执行该任务
}

当然在等待以及执行的时候,还有其他的过程,像是检查任务队列,判断任务是否达到执行时机,判断同步的上一个任务是不是执行完毕等等。
而宏任务的队列就是事件循环。而 JavaScript 在执行宏观任务的时候,如果这个任务是异步的, JavaScript 要保证异步代码在一个宏观任务中执行,那么宏观任务中就有微任务队列
有了宏任务以及微任务概念之后,就可以理解,引擎产生的任务会在任务尾部添加微任务。而宿主产生的任务会直接扔在宏任务内

promise

1
2
3
4
5
6
7
8
9
var promise = new Promise(function(resolve, reject){
console.log("a");
resolve();
})
promise.then(function(){
console.log("b");
});
console.log("c");
// 打印的顺序是 acb;

上面的运行结果之所是 acb 是因为 new 一个构造函数,会执行构造函数内的函数,然后虽然 console.log("c").then() 后面,但是因为 resolve() 是异步的。所以会先打印 c

setTimeout与promise混合使用

1
2
3
4
5
6
7
var promise = new Promise(function(resolve, reject){
console.log("a");
resolve()
});
setTimeout(()=>console.log("d"), 0)
promise.then(() => console.log("c"));
console.log("b")

上面的运行结果是 abcd ,代码从上向下扫射运行。先是运行 new 关键字的构造函数里的代码,打印 a ,遇到 resolve() 运行 .then 的代码,但是由于这个是异步的,所以就会继续往下运行代码, setTimeout 的代码会放在任务队列里。而事件队列里的代码会等主线程的代码运行完之后,才会检查事件队列。所以下一步打印的是 b ,然后由于 promiseJavaScript 引擎产生的微任务,而 setTimeout 是浏览器产生的宏任务,所有先打印 c 再打印 d

微任务是永远优先于宏任务的
1
2
3
4
5
6
7
8
9
10
11
12
setTimeout(()=>console.log("d"), 0)
var promise = new Promise(function(resolve, reject){
resolve()
});
promise.then(() => {
var begin = Date.now();
while(Date.now() - begin < 1000);
console.log("c1")
new Promise(function(resolve, reject){
resolve()
}).then(() => console.log("c2"))
});

上面的代码运行结果为 c1/c2/d

由上面的两个示例可以得出

  • 先确定有多少个宏任务、
  • 然后确定每个宏任务里有多少个微任务
  • 根据调用的循序,确定每个微任务的执行顺序
  • 然后将整个的环境执行顺序确定下来

评论加载中